
/*
 * Copyright (C) 2008-2009 Archie L. Cobbs. All rights reserved.
 *
 * $Id: DatabaseSupport.java 377 2010-03-11 20:50:18Z archie.cobbs $
 */

package org.dellroad.sidekar;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.dellroad.sidekar.schema.DatabaseUpdate;
import org.dellroad.sidekar.schema.model.SchemaModel;
import org.dellroad.sidekar.xml.XMLExporter;
import org.dellroad.sidekar.xml.XMLImporter;

import org.jibx.extras.DocumentComparator;
import org.jibx.runtime.JiBXException;

import org.xmlpull.v1.XmlPullParserException;

/**
 * Base class for unit tests that want to create a new {@link SidekarDatabase}
 * within a new {@link SidekarEnvironment} each time they run.
 */
public abstract class DatabaseSupport extends EnvironmentSupport {

    protected SidekarDatabase db;

    protected DatabaseSupport() {
    }

    /**
     * Get database name. This implementation returns the unqualified class name.
     */
    protected String getDatabaseName() {
        return getClass().getSimpleName();
    }

    protected void openDatabase(List<Class<?>> schemaClasses) {
        openDatabase(schemaClasses, Collections.<DatabaseUpdate>emptyList());
    }

    protected void openDatabase(List<Class<?>> schemaClasses, List<DatabaseUpdate> databaseUpdates) {
        openDatabase(new SchemaModel(schemaClasses), databaseUpdates);
    }

    protected void openDatabase(SchemaModel schemaModel) {
        openDatabase(schemaModel, Collections.<DatabaseUpdate>emptyList());
    }

    protected void openDatabase(SchemaModel schemaModel, List<DatabaseUpdate> databaseUpdates) {
        assert this.db == null;
        this.db = new SidekarDatabase(this.env, getDatabaseName(), schemaModel, databaseUpdates);
        boolean success = false;
        try {
            this.db.open();
            success = true;
        } finally {
            if (!success)
                this.db = null;
        }
    }

    protected void closeDatabase() {
        assert this.db != null;
        this.db.close();
        this.db = null;
    }

    protected <T> T getFirst(Class<T> c) {
        return c.cast(this.db.getEntity(c).iterator().next());
    }

    protected <T> Set<T> getAll(Class<T> c) {
        HashSet<T> set = new HashSet<T>();
        for (T obj : this.db.getEntity(c))
            set.add(obj);
        return set;
    }

    protected int count(Class<?> c) {
        int count = 0;
        for (Object obj : this.db.getEntity(c))
            count++;
        return count;
    }

    /**
     * Export database to XML, read it back in, and then export it again and compare the result to a file.
     * The database must be open.
     */
    protected void compareExport(String filename) {
        compareExport(this.db, filename);
    }

    /**
     * Export database to XML, read it back in, and then export it again and compare the result to a file.
     * The database must be open.
     */
    protected void compareExport(SidekarDatabase exportDB, String filename) {
        compareExport(exportDB, filename, true);
    }

    /**
     * Export database to XML, optionally read it back in and export it again, and compare the result to a file.
     * The database must be open.
     *
     * @param doubleExport whether to export twice
     */
    protected void compareExport(SidekarDatabase exportDB, String filename, boolean doubleExport) {
        try {

            // Export database
            File export = exportXML(exportDB, "export");

            // Do it again
            if (doubleExport) {

                // Read it back in into a new database
                String newName = exportDB.getName() + "_NEW";
                FileInputStream input = new FileInputStream(export);
                SidekarDatabase newDB = importXML(input, newName);
                input.close();

                // Export new database
                newDB.open();
                export = exportXML(newDB, "export");
                newDB.close();

                // Remove temporary database
                this.env.removeSidekarDatabase(newName);
            }

            // Compare results
            compareXML(filename, export);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Import database from an XML file resource.
     *
     * @param filename resource filename
     * @param name override name for the imported database (or null)
     */
    protected SidekarDatabase importXML(String filename, String name)
      throws IOException, JiBXException, XmlPullParserException {
        InputStream input = getClass().getResourceAsStream(filename);
        SidekarDatabase newdb = importXML(input, name);
        input.close();
        return newdb;
    }

    /**
     * Import database from an XML input stream.
     *
     * @param input XML input
     * @param name override name for the imported database (or null)
     */
    protected SidekarDatabase importXML(InputStream input, String name)
      throws IOException, JiBXException, XmlPullParserException {
        return XMLImporter.importDatabase(input, this.env, name);
    }

    /**
     * Export database to a temporary file and return that file.
     */
    protected File exportXML(SidekarDatabase db, String name) throws IOException, JiBXException {
        File tempFile = File.createTempFile("export.", "", this.tempdir);
        FileOutputStream out = new FileOutputStream(tempFile);
        XMLExporter.exportDatabase(out, db, name, false);
        out.close();
        return tempFile;
    }

    /**
     * Compare an XML file with a classpath resource.
     */
    protected void compareXML(String expected, File actual) throws IOException, JiBXException, XmlPullParserException {
        InputStream expectedInput = getClass().getResourceAsStream(expected);
        if (expectedInput == null)
            throw new RuntimeException("can't find resource \"" + expected + "\"");
        FileInputStream actualInput = new FileInputStream(actual);
        compareXML(expectedInput, actualInput, expected, actual.toString());
    }

    /**
     * Compare (then close) two XML inputs.
     */
    protected void compareXML(InputStream expected, InputStream actual, String expectedName, String actualName)
      throws IOException, JiBXException, XmlPullParserException {
        InputStreamReader expectedReader = new InputStreamReader(expected);
        InputStreamReader actualReader = new InputStreamReader(actual);
        boolean same = new DocumentComparator(System.out).compare(actualReader, expectedReader);
        assert same : "actual export \"" + actualName + "\" != expected export \"" + expectedName + "\"";
        expectedReader.close();
        actualReader.close();
    }
}

